<?php
/**
 * 
 * @author phuongtn1@fpt.net
 */
class Fpt_Business_News_Adapter_Redis extends Fpt_Business_News_Adapter_Abstract {

    /**
     * @var system_model Singleton instance
     */
    protected static $_instance;

    /**
     * List popular article
     * article_top_l:{categoryid}:article_type(hot|view|comment) = {article_id=>(hot_date|number_view|number_comment)}
     * list_top_news = 'article_top_l:%s:%s'
     */
    const LIST_TOP_NEWS = "list_top_news";
     /**
     * List popular article
     * article_top_l:{categoryid} = {article_id=>hotorder}
     * hotorder = (public_time) + (priority * 86400)
     * list_top_news_cate = 'list_top_news_cate:%s'
     */
    const LIST_TOP_NEWS_CATE = "list_top_news_cate";
    
    /**
     * List article by cate 
     * list_cate_new_l:{cate_id} ={article_id=>publish_date}
     * list_cate_news = CACHING_PREFIX 'list_cate_new_l:%s'
     */
    const LIST_CATE_NEWS = "list_cate_news";
    /**
     * List article by topic
     * Article_top_l:{topic_id}={article_id=>post_date}
     * list_topic_news = CACHING_PREFIX 'topic_article_l:%s'
     */
    const LIST_TOPIC_NEWS = "list_topic_news";
    /**
     * List lastest artilce of team. Ex: last news about MU
     * list_news_by_team = CACHING_PREFIX 'list_news_by_team_l:%s' 
     */
    const LIST_TEAM_NEWS = "list_news_by_team";
    
    const LIST_TAG_NEWS = "list_news_by_tag";
    /**
     * List lastest artilce with type = video of 1 tagid: list_video_by_tag:{tag_id} = {articleID => publish_date}
     * can not use list_news_by_tag : related media
     */
    const LIST_VIDEO_TAG_NEWS = "list_video_by_tag";
    
    /**
     * 1 lastest news in all cate
     * list_news_all = CACHING_PREFIX 'list_news_all'  
     */
    const LIST_NEWS_ALL = "list_news_all";
    /**
     * List reference by article id: list_object_reference_l:{article_id} = {referenceid=>date} 
     * list_object_reference = CACHING_PREFIX 'list_object_reference_l:%s'
     */
    const LIST_OBJECT_REFERENCES = "list_object_reference";
    
    /**
     * List object cates: list_object_categories:{article_id} = {cate_id=>date} 
     */
    const LIST_OBJECT_CATEGORIES = "list_object_categories";
    
    /**
     * List object hot info: list_object_hot:{article_id} = {cate_id=>hot_order}
     */
    const LIST_OBJECT_HOTS = "list_object_hots";
    
    /**
     * List object hot info: list_object_tags:{article_id} = {tag_id=>hot_order}
     */
    const LIST_OBJECT_TAGS = "list_object_tags";
    /**
     * List topic's article =>HASH
     * List object topics: list_object_topics:{article_id} = {topic_article_date=>array(topic_id, row)}
     */
    const LIST_OBJECT_TOPICS = "list_object_topics";
    /**
     * 
     */
    
    const MAX_ARTICLE_ID = "max_article_id";
    const SCHEDULE_ARTICLES = "schedule_articles";
    const LIST_OTHER_NEWS   = "list_other_news";
    const LIST_LATEST_NEWS_ON_CATE = "list_latest_news_on_cate";
  
    /**
     * number for sharding redis db => list thethao 
     */
    const SHARDING_DB_ALL = 1;
    /**
     * define default max item in 1 list
     */
    const REDIS_MAX_LENGTH = 500;

    /**
     * Avoid directly create instance
     */
    protected function __construct() {
        
    }

    /**
     * Retrieve singleton instance
     *
     * @return system_model
     */
    public static function getInstance() {
        if (null === self::$_instance) {
            self::$_instance = new self();
        }
        return self::$_instance;
    }

    /**
     * Reset the singleton instance
     *
     * @return void
     */
    public static function resetInstance() {
        self::$_instance = null;
    }

    /**
     * get list article order by priority_score
     * @param int $categoryID
     * @param int $offset
     * @param int $limit
     * @return array 
     * @author PhuongTN
     */
    public function getListTopArticle($categoryID, $offset, $limit)
    {
        //default return
        $return = array('total' => 0, 'data' => array());
        try
        {
            //get instance redis for this cate
            $redisInstance = Fpt_Global::getRedisInstance(0, $categoryID);
            
            if($redisInstance != false)
            {
                //get key cache
                $keyCate    = Fpt_Global::makeCacheKey(self::LIST_TOP_NEWS_CATE, $categoryID);
                //get total
                $return['total']   = intval($redisInstance->zCount($keyCate, '-inf', '+inf'));
                //check total data
                if($return['total'] > 0)
                {
                    //get end score
                    $end = Fpt_Global::getRedisPaging($offset, $limit);
                    //get revert score: latest will be show first
                    $return['data']   = $redisInstance->zRevRange($keyCate, $offset, $end);
                }
            }
        }
        catch(Exception $ex)
        {
            //log error
            Fpt_Log::sendlog(4, $ex->getCode(), $ex->getMessage(), $ex->getTraceAsString(), null, null);
        }
        return $return;
    }
    
    /**
     * get list other article order by time parent
     * @param int $categoryID
     * @param int $offset
     * @param int $limit
     * @return array 
     * @author PhuongTN
     */
    public function getLatestNewsOnCate ($categoryID, $offset, $limit)
    {
        //default return
        $return = array('total' => 0, 'data' => array());
        try
        {
            //get instance redis for this cate
            $redisInstance = Fpt_Global::getRedisInstance(0, $categoryID);
            if($redisInstance !== false)
            {
                //get key cache
                $keyCate    = Fpt_Global::makeCacheKey(self::LIST_LATEST_NEWS_ON_CATE, $categoryID);
                //get total
                $return['total']   = intval($redisInstance->zCount($keyCate, '-inf', '+inf'));
                //check total data
                if($return['total'] > 0)
                {
                    //get end score
                    $end = Fpt_Global::getRedisPaging($offset, $limit);
                    //get revert score: latest will be show first
                    $return['data']   = $redisInstance->zRevRange($keyCate, $offset, $end);
                }
            }
        }
        catch(Exception $ex)
        {
            //log error
            Fpt_Log::sendlog(4, $ex->getCode(), $ex->getMessage(), $ex->getTraceAsString(), null, null);
        }
        return $return;
    }
    
    /**
     * get list other article order by time parent
     * @param int $categoryID parent cateid
     * @param int $offset
     * @param int $limit
     * @return array 
     * @author PhuongTN
     */
    public function getListOtherNews($categoryID, $timeEnd, $offset, $limit)
    {
                //default return
        $return = array('total' => 0, 'data' => array());
        try
        {
            //get instance redis for this cate
            $redisInstance = Fpt_Global::getRedisInstance(0, $categoryID);
            if($redisInstance !== false)
            {
                //format params
                $timeStart  = 0;
                $timeEnd    = intval($timeEnd);
                //get key cache
                $keyCate    = Fpt_Global::makeCacheKey(self::LIST_OTHER_NEWS, $categoryID);
                //get start & end score
                $score = Fpt_Global::getRedisPaging($timeStart, $timeEnd, true, true);
                $timeStart  = $score['startScore'];
                $timeEnd    = $score['endScore'];
                //get total
                $return['total']   = intval($redisInstance->zCount($keyCate, $timeStart, $timeEnd));
                //check total data
                if($return['total'] > 0)
                {
                    $data = $redisInstance->zRevRangeByScore($keyCate, $timeEnd, $timeStart,  array('withscores' => $withScore, 'limit' => array($offset, $limit)));
                    $return['data'] = (is_array($data)) ? $data : array();
                }
            }
        }
        catch(Exception $ex)
        {
            //log error
            Fpt_Log::sendlog(4, $ex->getCode(), $ex->getMessage(), $ex->getTraceAsString(), null, null);
        }
        return $return;
    }
    
    
    /**
     * get list article order by time (now:only for breakum news)
     * @param int $categoryID
     * @param int $offset
     * @param int $limit
     * @return array 
     * @author PhuongTN
     */
    public function getListArticleByTime($categoryID, $offset, $limit)
    {
        //default return
        $return = array('total' => 0, 'data' => array());
        try
        {
            //get instance redis for this cate
            $redisInstance = Fpt_Global::getRedisInstance(0, self::SHARDING_DB_ALL);
            if($redisInstance !== false)
            {
                //get key cache
                $keyCate    = Fpt_Global::makeCacheKey(self::LIST_NEWS_ALL, $categoryID);
                //get total
                $return['total']   = intval($redisInstance->zCount($keyCate, '-inf', '+inf'));
                //check total data
                if($return['total'] > 0)
                {
                    //get end score
                    $end = Fpt_Global::getRedisPaging($offset, $limit);
                    //get revert score: latest will be show first
                    $return['data']   = $redisInstance->zRevRange($keyCate, $offset, $end);
                }
            }
        }
        catch(Exception $ex)
        {
            //log error
            Fpt_Log::sendlog(4, $ex->getCode(), $ex->getMessage(), $ex->getTraceAsString(), null, null);
        }
        return $return;
    }
    
    /**
     * get article by cate + public time order by priority_score
     * @param int $categoryID cateid not null
     * @param int $timeStart time start (unixtime) not null
     * @param int $timeEnd time end; let it = 0 when don't have exact timeend
     * @param int $offset offset -> mysql
     * @param int $limit limit -> mysql
     * @param bool $withScore get score or not
     * @return array 
     * @author PhuongTN
     */
    public function getArticleByCatePublicTime($categoryID, $timeEnd, $timeStart, $offset = 0, $limit = 5, $withScore = FALSE)
    {
        //default return
        $return = array('total' => 0, 'data' => array());
        try
        {
            //get instance redis for this cate
            $redisInstance = Fpt_Global::getRedisInstance(0, $categoryID);
            if($redisInstance !== false)
            {
                //format params
                $timeStart  = intval($timeStart);
                if($timeStart > 0)
                {
                    //get hot score: publish_date.priority.publish_time
                    $publishDate = date('Ymd', $timeStart);
                    $publishTime = date('His', $timeStart);
                    $timeStart = $publishDate.'05'.$publishTime;
                }  
                
                $timeEnd    = intval($timeEnd);
                if($timeEnd > 0)
                {
                    //get hot score: publish_date.priority.publish_time
                    $publishDate = date('Ymd', $timeEnd);
                    $publishTime = date('His', $timeEnd);
                    $timeEnd = $publishDate.'09'.$publishTime;
                }  
                

                //get key cache
                $keyCate    = Fpt_Global::makeCacheKey(self::LIST_TOP_NEWS_CATE, $categoryID);
                //get start & end score
                $score = Fpt_Global::getRedisPaging($timeStart, $timeEnd, true, true);
                $timeStart  = $score['startScore'];
                $timeEnd    = $score['endScore'];
                //get total
                $return['total']   = intval($redisInstance->zCount($keyCate, $timeStart, $timeEnd));
                //check total data
                if($return['total'] > 0)
                {
                    $data = $redisInstance->zRevRangeByScore($keyCate, $timeEnd, $timeStart,  array('withscores' => $withScore, 'limit' => array($offset, $limit)));
                    $return['data'] = (is_array($data)) ? $data : array();
                }
            }
        }
        catch(Exception $ex)
        {
            //log error
            Fpt_Log::sendlog(4, $ex->getCode(), $ex->getMessage(), $ex->getTraceAsString(), null, null);
        }
        return $return;
    }
    
    /**
     * add redis topic's articles
     * @param int $topicID
     * @param array $arrDetails ('public_time','topic_config' => array(full config from topic_article)
     * @return array 
     */
    public function addTopicArticles($topicID, $arrDetails)
    {
        $return = array();
        try
        {
            //get instance redis
            $redisTopicInstance = Fpt_Global::getRedisInstance(0, $topicID);
            if($redisTopicInstance !== false)
            {
                $keyTopic  = Fpt_Global::makeCacheKey(self::LIST_TOPIC_NEWS, $topicID);
                //start redis pipeline
                $redisPipe = $redisTopicInstance->pipeline();
                if(is_array($arrDetails))
                {
                    foreach($arrDetails as $articleID => $articleDetail)
                    {
                        $articleID = intval($articleID);
                        $redisPipe->zAdd($keyTopic, intval($articleDetail['public_time']), $articleID);
                    }
                    //trim LIST: keep last REDIS_MAX_LENGTH item
                    $redisPipe->zRemRangeByRank($keyTopic, 0, -self::REDIS_MAX_LENGTH);  
                    //exec pipe
                    $return = $redisPipe->exec();
                    
                    //loop article list again: cause sharding by topic vs article, save RTT to redis
                    foreach($arrDetails as $articleID => $articleDetail)
                    {
                        $articleID = intval($articleID);
                        $redisInstance      = Fpt_Global::getRedisInstance(0, $articleID);
                        $keyTopicArticle    = Fpt_Global::makeCacheKey(self::LIST_OBJECT_TOPICS, $articleID);
                        $return[]   = $redisInstance->hSet($keyTopicArticle, $articleDetail['topic_config']['topic_article_id'], Zend_Json::encode($articleDetail['topic_config']));
                    }
                }//end check array detail
            }//end check redis ok
        }
        catch(Exception $ex)
        {
            //log error
            Fpt_Log::sendlog(4, $ex->getCode(), $ex->getMessage(), $ex->getTraceAsString(), null, null);
        }
        return $return;
    }
    
    /**
     * Delete topic articles
     * @param int $topicID
     * @param array $arrDetails
     * @return array 
     */
    public function deleteTopicArticles($topicID, $arrDetails, $delTopic = false)
    {
        try
        {
            $return = array();
            //get instance redis
            $redisTopicInstance = Fpt_Global::getRedisInstance(0, $topicID);
            if($redisTopicInstance !== false)
            {
                $keyTopic  = Fpt_Global::makeCacheKey(self::LIST_TOPIC_NEWS, $topicID);
                $redisPipe = $redisTopicInstance->pipeline();
                if(is_array($arrDetails))
                {
                    foreach($arrDetails as $articleID => $articleDetail)
                    {
                        $articleID = intval($articleID);
                        $redisPipe->zDelete($keyTopic, $articleID);
                    }
                    //exec pipe
                    $return = $redisPipe->exec();

                    //loop article list again: cause sharding by topic vs article, save RTT to redis
                    foreach($arrDetails as $articleID => $articleDetail)
                    {
                        $articleID = intval($articleID);
                        $redisInstance      = Fpt_Global::getRedisInstance(0, $articleID);
                        $keyTopicArticle    = Fpt_Global::makeCacheKey(self::LIST_OBJECT_TOPICS, $articleID);
                        //delete key
                        $redisInstance->delete($keyTopicArticle);
                        //if still have another topic
                        if(isset($articleDetail['topic_config']))
                        {
                            foreach($articleDetail['topic_config'] as $topicArticleDetail)
                            {
                                $return[]   = $redisInstance->hSet($keyTopicArticle, $topicArticleDetail['topic_article_id'], Zend_Json::encode($topicArticleDetail));
                            }
                        }//end check add another topic
                    }//end foreach detail
                    if($delTopic === true)
                    {
                        $return[] = $redisTopicInstance->delete($keyTopic);
                        return $return;
                    }
                }//end check valid detail
            }//end check redis connect     
        }
        catch(Exception $ex)
        {
            //log error
            Fpt_Log::sendlog(4, $ex->getCode(), $ex->getMessage(), $ex->getTraceAsString(), null, null);
        }
        return $return;
    }
    
    /**
     * get list article by topic order by time
     * @param int $topicID
     * @param int $offset
     * @param int $limit
     * @return array 
     * @author PhuongTN
     */
    public function getListArticleByTopic($topicID, $offset, $limit)
    {
        //default return
        $return = array();
        try
        {
            //get instance redis for this cate
            $redisInstance = Fpt_Global::getRedisInstance(0, $topicID);
            if($redisInstance != false)
            {
                //get key cache
                $keyTopic    = Fpt_Global::makeCacheKey(self::LIST_TOPIC_NEWS, $topicID);
                //get total
                $return['total']   = intval($redisInstance->zCount($keyTopic, '-inf', '+inf'));
                //check total data
                if($return['total'] > 0)
                {
                    //get end score
                    $end = Fpt_Global::getRedisPaging($offset, $limit);
                    //get revert score: latest will be show first
                    $return['data']   = $redisInstance->zRevRange($keyTopic, $offset, $end);
                }
            }
        }
        catch(Exception $ex)
        {
            //log error
            Fpt_Log::sendlog(4, $ex->getCode(), $ex->getMessage(), $ex->getTraceAsString(), null, null);
        }
        return $return;
    }

    /**
     * get list article by tag order by time
     * @param int $tagID
     * @param int $offset
     * @param int $limit
     * @return array
     * @author kietthm
     */
    public function getListArticleByTag($tagID, $offset, $limit, $articleType)
    {
        //default return
        $return = array();
        try
        {
            //get instance redis for this cate
            $redisInstance = Fpt_Global::getRedisInstance(0, $tagID);

            if($redisInstance != false)
            {
                if($articleType == VIDEO)
                {
                    $keyTag  = Fpt_Global::makeCacheKey(self::LIST_VIDEO_TAG_NEWS, $tagID);
                }
                elseif($articleType == ARTICLE)
                {
                    $keyTag  = Fpt_Global::makeCacheKey(self::LIST_TEAM_NEWS, $tagID);
                }
                else
                {
                    //get key cache
                    $keyTag    = Fpt_Global::makeCacheKey(self::LIST_TAG_NEWS, array($tagID,$articleType));
                }

                //get total
                $return['total']   = intval($redisInstance->zCount($keyTag, '-inf', '+inf'));
                //check total data
                if($return['total'] > 0)
                {
                    //get end score
                    $end = Fpt_Global::getRedisPaging($offset, $limit);
                    //get revert score: latest will be show first
                    $return['data']   = $redisInstance->zRevRange($keyTag, $offset, $end);
                }
            }
        }
        catch(Exception $ex)
        {
            //log error
            Fpt_Log::sendlog(4, $ex->getCode(), $ex->getMessage(), $ex->getTraceAsString(), null, null);
        }
        return $return;
    }
    
    /**
     * hot_id, hot_type, category_id, article_id, author_id, site_id, display_order, `status`, showed_area, creation_time, update_time
     * @param array $hotInfo 
     * @return array 
     */
    public function setHotArticle($hotInfo)
    {        
        $return = array();
        try
        {
            foreach($hotInfo as $hotDetail)
            {
                //hot order
                $hotOrder = intval($hotDetail['display_order']);
                $arrayHot = array($hotDetail['category_id'], 'hot');
                $redisHotInstance = Fpt_Global::getRedisInstance(0, $hotDetail['category_id']);
                if($redisHotInstance != false)
                {
                    //add list HOT news on cate
                    $keyHot    = Fpt_Global::makeCacheKey(self::LIST_TOP_NEWS, $arrayHot);
                    $hotOrderInfo = $redisHotInstance->zRangeByScore($keyHot, '-inf', '+inf', array('withscores' => TRUE, 'limit' => array(0,1)));
                    if($hotOrderInfo != false)
                    {
                        $hotOrder = array_values($hotOrderInfo);
                        $hotOrder = intval($hotOrder[0]) - 1;
                    }

                    $return[]  = $redisHotInstance->zAdd($keyHot, $hotOrder, $hotDetail['article_id']);
                    //trim LIST: last REDIS_MAX_LENGTH item
                    $redisHotInstance->zRemRangeByRank($keyHot, self::REDIS_MAX_LENGTH, -1);

                    $redisAricleInstance = Fpt_Global::getRedisInstance(0, $hotDetail['article_id']);
                    $keyHotCate = Fpt_Global::makeCacheKey(self::LIST_OBJECT_HOTS, $hotDetail['article_id']);
                    $return[]   = $redisAricleInstance->zAdd($keyHotCate,  $hotOrder, $hotDetail['category_id']);
                    //need all, don't trim list!
                }  
            }
        }
        catch(Exception $ex)
        {
            //log error
            Fpt_Log::sendlog(4, $ex->getCode(), $ex->getMessage(), $ex->getTraceAsString(), null, null);
        }
        return $return;
    }
    
    /**
     * hot_id, hot_type, category_id, article_id, author_id, site_id, display_order, `status`, showed_area, creation_time, update_time
     * @param int $articleID 
     * @param int $cateHot 
     * @return array 
     */
    public function unsetHotArticle($articleID, $cateHot = SITE_ID)
    {        
        $return = array();
        try
        {
            //init redis instance
            $redisHotInstance = Fpt_Global::getRedisInstance(0, $cateHot);
            if($redisHotInstance != false)
            {
                //format params
                $arrayHot   = array($cateHot, 'hot');
                $keyHot     = Fpt_Global::makeCacheKey(self::LIST_TOP_NEWS, $arrayHot);
                //add list HOT news on cate
                $return[]   = $redisHotInstance->zDelete($keyHot,  $articleID);  
            }
        }
        catch(Exception $ex)
        {
            //log error
            Fpt_Log::sendlog(4, $ex->getCode(), $ex->getMessage(), $ex->getTraceAsString(), null, null);
        }
        return $return;
    }
    
    /**
     * Update all position in cateID
     * @param int $cateID
     * @param array $arrIDs
     * @return type 
     */
    public function updateHotByCate($cateID, $arrIDs)
    {        
        $return = array();
        try
        {
            //get redis instance by cate
            $redisHotInstance = Fpt_Global::getRedisInstance(0, $cateID);
            if($redisHotInstance !== false)
            {
                 //init key
                $arrayHot = array($cateID, 'hot');
                $keyHot   = Fpt_Global::makeCacheKey(self::LIST_TOP_NEWS, $arrayHot);
                //delete old key
                $return[] = $redisHotInstance->delete($keyHot);

                foreach($arrIDs as $hotOrder => $articleID)
                {
                    $hotOrder++;
                    //add list HOT news on cate
                    $return[]  = $redisHotInstance->zAdd($keyHot, $hotOrder, $articleID);
                }
                //trim LIST: last REDIS_MAX_LENGTH item
                $redisHotInstance->zRemRangeByRank($keyHot, self::REDIS_MAX_LENGTH, -1);
            }
        }
        catch(Exception $ex)
        {
            //log error
            Fpt_Log::sendlog(4, $ex->getCode(), $ex->getMessage(), $ex->getTraceAsString(), null, null);
        }
        return $return;
    }
    
    /**
     * Add article redis
     * @param array $articleDetail array(articleID, categoryID, parentCateID, publish_time, 
     * teamID, hotOrder, topicID)
     * @return type 
     * @author PhuongTN
     */
    public function addArticle($articleDetail)
    {
        $return = false;
        try
        {
            //check is hot homepage
            if(!empty($articleDetail['hotInfo']))
            {
                $return[] = $this->setHotArticle($articleDetail['hotInfo'], $articleDetail['article_type']);
            }//end check is hot
            
            //get hot score: publish_date.priority.publish_time
            $publishDate = date('Ymd', intval($articleDetail['publish_time']));
            $publishTime = date('His', intval($articleDetail['publish_time']));
            if(intval($articleDetail['priority']) < 10)
            {
                $articleDetail['priority'] = '0'.$articleDetail['priority'];
            }
            $articleScore = $publishDate.$articleDetail['priority'].$publishTime;
            //check show on folder or not
            if($articleDetail['show_status'] == 1)
            {
                //loop categories
                foreach($articleDetail['categories'] as $cateID => $cateDetail)
                {
                    //add list news on cate
                    $redisInstance = Fpt_Global::getRedisInstance(0, $cateID);
                    $keyCate    = Fpt_Global::makeCacheKey(self::LIST_TOP_NEWS_CATE, $cateID);
                    $return[]   = $redisInstance->zAdd($keyCate, $articleScore, $articleDetail['article_id']);     
//                    if($articleDetail['article_type'] != VIDEO && $articleDetail['article_type'] != ALBUM)
//                    {
//                        //trim list: keep last REDIS_MAX_LENGTH item
//                        $redisInstance->zRemRangeByRank($keyCate, 0, -self::REDIS_MAX_LENGTH);
//                    }
                    //orther news
                    $keyCate    = Fpt_Global::makeCacheKey(self::LIST_OTHER_NEWS, $cateID);
                    $return[]   = $redisInstance->zAdd($keyCate, $articleDetail['publish_time'], $articleDetail['article_id']);     
//                    $redisInstance->zRemRangeByRank($keyCate, 0, -self::REDIS_MAX_LENGTH);
                    //news order by time
                    $keyCate    = Fpt_Global::makeCacheKey(self::LIST_LATEST_NEWS_ON_CATE, $cateID);
                    $return[]   = $redisInstance->zAdd($keyCate, $articleDetail['publish_time'], $articleDetail['article_id']);     
//                    $redisInstance->zRemRangeByRank($keyCate, 0, -self::REDIS_MAX_LENGTH);
                    
                    if(!empty($cateDetail['full_parent']))
                    {
                        //loop cate parent
                        foreach($cateDetail['full_parent'] as $cateParentID)
                        {
                            if($cateParentID != SITE_ID)
                            {
                                $redisInstance = Fpt_Global::getRedisInstance(0, $cateParentID);
                                $keyCate    = Fpt_Global::makeCacheKey(self::LIST_OTHER_NEWS, $cateParentID);
                                $return[]   = $redisInstance->zAdd($keyCate, $articleDetail['publish_time'], $articleDetail['article_id']);     
//                                $redisInstance->zRemRangeByRank($keyCate, 0, -self::REDIS_MAX_LENGTH);
                            }
                        }
                    }
                    
                    //get instance redis for article
                    $redisArticleInstance = Fpt_Global::getRedisInstance(0, $articleDetail['article_id']);
                    //add list news on parent cate
                    $keyArticle  = Fpt_Global::makeCacheKey(self::LIST_OBJECT_CATEGORIES, $articleDetail['article_id']);
                    $return[]   = $redisArticleInstance->zAdd($keyArticle, $articleDetail['publish_time'], $cateID);
                    //trim LIST: keep last REDIS_MAX_LENGTH item
                    $redisArticleInstance->zRemRangeByRank($keyArticle, 0, -self::REDIS_MAX_LENGTH);
                }//end loop categories
            }
            

            //check array topic
            if(is_array($articleDetail['topicIDs']))
            {
                foreach($articleDetail['topicIDs'] as $key => $topicDetail)
                {
                    $topicID = $topicDetail['topic_id'];
                    //get instance redis
                    $redisTopicInstance = Fpt_Global::getRedisInstance(0, $topicID);
                    $keyTopic  = Fpt_Global::makeCacheKey(self::LIST_TOPIC_NEWS, $topicID);
                    $return[]  = $redisTopicInstance->zAdd($keyTopic, $articleScore, $articleDetail['article_id']);
                    //trim LIST: keep last REDIS_MAX_LENGTH item
                    $redisTopicInstance->zRemRangeByRank($keyTopic, 0, -self::REDIS_MAX_LENGTH);         
                   
                    //@todo: optimize multi
                    $redisInstance      = Fpt_Global::getRedisInstance(0, $articleDetail['article_id']);
                    $keyTopicArticle    = Fpt_Global::makeCacheKey(self::LIST_OBJECT_TOPICS, $articleDetail['article_id']);
                    $return[]   = $redisInstance->hSet($keyTopicArticle, $topicDetail['topic_article_id'], Zend_Json::encode($topicDetail));
                }//end foreach topic ids
            }//end check topic
            
            //check team
            if($articleDetail['tagIDs'])
            {
                foreach($articleDetail['tagIDs'] as $tagID)
                {
                    //get instance redis
                    $redisTeamInstance = Fpt_Global::getRedisInstance(0, $tagID);
                    $keyTeam  = Fpt_Global::makeCacheKey(self::LIST_TAG_NEWS, array($tagID, $articleDetail['article_type']) );
                    $return[] = $redisTeamInstance->zAdd($keyTeam, $articleScore, $articleDetail['article_id']);
                    //trim LIST: keep last REDIS_MAX_LENGTH item
                    $redisTeamInstance->zRemRangeByRank($keyTeam, 0, -self::REDIS_MAX_LENGTH);
                    
                    if($articleDetail['article_type'] == VIDEO)
                    {
                        $keyTeam  = Fpt_Global::makeCacheKey(self::LIST_VIDEO_TAG_NEWS, $tagID);
                        $return[] = $redisTeamInstance->zAdd($keyTeam, $articleScore, $articleDetail['article_id']);
                        //trim LIST: keep last REDIS_MAX_LENGTH item
                        $redisTeamInstance->zRemRangeByRank($keyTeam, 0, -self::REDIS_MAX_LENGTH);
                    }
                    elseif($articleDetail['article_type'] == ARTICLE)
                    {
                        $keyTeam  = Fpt_Global::makeCacheKey(self::LIST_TEAM_NEWS, $tagID);
                        $return[] = $redisTeamInstance->zAdd($keyTeam, $articleScore, $articleDetail['article_id']);
                        //trim LIST: keep last REDIS_MAX_LENGTH item
                        $redisTeamInstance->zRemRangeByRank($keyTeam, 0, -self::REDIS_MAX_LENGTH);
                    }
                    
                     //get instance redis for parent cate
                    $redisArticleInstance = Fpt_Global::getRedisInstance(0, $articleDetail['article_id']);
                    //add list news on parent cate
                    $keyArticle  = Fpt_Global::makeCacheKey(self::LIST_OBJECT_TAGS, $articleDetail['article_id']);
                    $return[]   = $redisArticleInstance->zAdd($keyArticle, $articleDetail['publish_time'], $tagID);
                    //trim LIST: keep last REDIS_MAX_LENGTH item
                    $redisArticleInstance->zRemRangeByRank($keyArticle, 0, -self::REDIS_MAX_LENGTH);
                }//end foreach team
            }//end check team
            
            //check have reference or not
            if(!empty($articleDetail['objectReference']))
            {
                //get instance redis
                $redisReferInstance = Fpt_Global::getRedisInstance(0, $articleDetail['article_id']);
                $keyRefer  = Fpt_Global::makeCacheKey(self::LIST_OBJECT_REFERENCES, $articleDetail['article_id']);
                foreach($articleDetail['objectReference'] as $referid =>$objectRefer)
                {
                    $return[] = $redisReferInstance->zAdd($keyRefer, intval($objectRefer['display_order']), $objectRefer['reference_id']);
                }
                //trim LIST: keep last REDIS_MAX_LENGTH item
                $redisReferInstance->zRemRangeByRank($keyRefer, 0, -self::REDIS_MAX_LENGTH);
            }
            
            //get redis all instance
            $redisAllInstance = Fpt_Global::getRedisInstance(0, self::SHARDING_DB_ALL);
            $keyNew     = Fpt_Global::makeCacheKey(self::LIST_NEWS_ALL, self::SHARDING_DB_ALL);
            $return[]   = $redisAllInstance->zAdd($keyNew, $articleDetail['publish_time'], $articleDetail['article_id']);
            //trim list latest news in all cate
            $redisAllInstance->zRemRangeByRank($keyNew, 0, -self::REDIS_MAX_LENGTH);
            
            //remove from 
            $redisInstance  = Fpt_Global::getRedisInstance();
            $keyCate        = Fpt_Global::makeCacheKey(self::SCHEDULE_ARTICLES);
            $redisInstance->zDelete($keyCate, $articleDetail['article_id']);
            
            $redisInstance->bgSave();
            foreach($return as $rs)
            {
                if($rs === false)
                {
                    return false;
                }
            }
        }
        catch(Exception $ex)
        {
            //log error
            Fpt_Log::sendlog(4, $ex->getCode(), $ex->getMessage(), $ex->getTraceAsString(), null, null);
        }
        return $return;
    }
    
    /**
     * Delete article redis
     * @param array $articleDetail array(articleID, categoryID, parentCateID, publish_time, 
     * teamID, hotOrder, topicID)
     * @return bool
     * @author PhuongTN 
     */
    public function deleteArticle($articleDetail)
    {
        $return = false;
        try
        {
            //loop categories
            foreach($articleDetail['categories'] as $cateID => $cateDetail)
            {
                $redisInstance = Fpt_Global::getRedisInstance(0, $cateID);
                $keyCate    = Fpt_Global::makeCacheKey(self::LIST_TOP_NEWS_CATE, $cateID);
                $return[]   = $redisInstance->zDelete($keyCate, $articleDetail['article_id']);  
                //orther news
                $keyCate    = Fpt_Global::makeCacheKey(self::LIST_OTHER_NEWS, $cateID);
                $return[]   = $redisInstance->zDelete($keyCate, $articleDetail['article_id']);     
                //news order by time
                $keyCate    = Fpt_Global::makeCacheKey(self::LIST_LATEST_NEWS_ON_CATE, $cateID);
                $return[]   = $redisInstance->zDelete($keyCate, $articleDetail['article_id']);     
                
                if(!empty($cateDetail['full_parent']))
                {
                    //loop cate parent
                    foreach($cateDetail['full_parent'] as $cateParentID)
                    {
                        if($cateParentID != SITE_ID)
                        {
                            $redisInstance = Fpt_Global::getRedisInstance(0, $cateParentID);
                            $keyCate    = Fpt_Global::makeCacheKey(self::LIST_OTHER_NEWS, $cateParentID);
                            $return[]   = $redisInstance->zDelete($keyCate, $articleDetail['article_id']);     
                        }
                    }
                }
            }//end loop categories
            //check is hot
            if(!empty($articleDetail['hotInfo']))
            {
                $return[]     = $this->unsetHotArticle($articleDetail['article_id']);

            }//end check is hot
            
            
            //check array topic
            if(is_array($articleDetail['topicIDs']))
            {
                foreach($articleDetail['topicIDs'] as $topicID)
                {
                    //get instance redis
                    $redisTopicInstance = Fpt_Global::getRedisInstance(0, $topicID);
                    $keyTopic  = Fpt_Global::makeCacheKey(self::LIST_TOPIC_NEWS, $topicID);
                    $return[]  = $redisTopicInstance->zDelete($keyTopic, $articleDetail['article_id']);
                    
                }//end foreach topic ids
                
            }//end check topic
            
            //check team
            if($articleDetail['tagIDs'])
            {
                foreach($articleDetail['tagIDs'] as $tagID)
                {
                    //get instance redis
                    $redisTeamInstance = Fpt_Global::getRedisInstance(0, $tagID);
                    $keyTeam  = Fpt_Global::makeCacheKey(self::LIST_TAG_NEWS, array($tagID, $articleDetail['article_type']));
                    $return[] = $redisTeamInstance->zDelete($keyTeam, $articleDetail['article_id']);
                }//end foreach team
                
            }//end check team
            
            //del reference key
            //get instance redis
            $redisInstance = Fpt_Global::getRedisInstance(0, $articleDetail['article_id']);
            $keyRefer  = Fpt_Global::makeCacheKey(self::LIST_OBJECT_REFERENCES, $articleDetail['article_id']);
            //delete key reference of this article
            $return[] = $redisInstance->delete($keyRefer);
            //delete list news on parent cate
            $keyArticle  = Fpt_Global::makeCacheKey(self::LIST_OBJECT_CATEGORIES, $articleDetail['article_id']);
            $return[]   = $redisInstance->delete($keyArticle);
            
            $keyArticle  = Fpt_Global::makeCacheKey(self::LIST_OBJECT_TAGS, $articleDetail['article_id']);
            $return[]   = $redisInstance->delete($keyArticle);
                  
            //del key article's topic
            $keyTopicArticle    = Fpt_Global::makeCacheKey(self::LIST_OBJECT_TOPICS, $articleDetail['article_id']);
            $return[]   = $redisInstance->delete($keyTopicArticle);
  
            $keyHot  = Fpt_Global::makeCacheKey(self::LIST_OBJECT_HOTS, $articleDetail['article_id']);
            $return[]   = $redisInstance->delete($keyHot);
            //get redis all instance
            $redisAllInstance = Fpt_Global::getRedisInstance(0, self::SHARDING_DB_ALL);
            $keyNew     = Fpt_Global::makeCacheKey(self::LIST_NEWS_ALL, self::SHARDING_DB_ALL);
     
            $return[]   = $redisAllInstance->zDelete($keyNew, $articleDetail['article_id']);
            
            $redisAllInstance->bgSave();
            
            foreach($return as $rs)
            {
                if($rs === false) 
                {
                    return false;
                }
            }
        }
        catch(Exception $ex)
        {
            //log error
            Fpt_Log::sendlog(4, $ex->getCode(), $ex->getMessage(), $ex->getTraceAsString(), null, null);
        }
        return $return;
    }

    /**
     * Function get tags of artilce
     * @param int $intArticleID
     * @return array
     * @author  : nhanva@fpt.net
     */
    public function getTagsByArticleID($articleID, $offset = 0, $limit = 0)
    {
       try 
        {
            $return = array();
            //get instance redis
            $redisReferInstance = Fpt_Global::getRedisInstance(0, $articleID);
            if($redisReferInstance !== false)
            {
                //get key cache
                $keyTag  = Fpt_Global::makeCacheKey(self::LIST_OBJECT_TAGS, $articleID);
                //count total
                $total = intval($redisReferInstance->zCount($keyTag, '-inf', '+inf'));
                //check total
                if($total > 0)
                {
                    //get redis page
                    $end = Fpt_Global::getRedisPaging($offset, $limit);
                    $return = $redisReferInstance->zRevRange($keyTag, $offset, $end);
                }
            }
        } 
        catch (Exception $ex) 
        {
            //log error
            Fpt_Log::sendlog(4, $ex->getCode(), $ex->getMessage(), $ex->getTraceAsString(), null, null);
        }
        return $return;
    }

    /**
     * get list article by team
     * @param int $teamID
     * @param int $offset
     * @param int $limit
     * @return array
     * @author TriNT
     */
    public function getListArticleByTeam($teamID, $offset = 0, $limit)
    {
        //default return
        $return = array('total' => 0, 'data' => NULL);
        try
        {
            //get instance redis for this cate
            $redisInstance = Fpt_Global::getRedisInstance(0, $teamID);
            if($redisInstance !== false)
            {
                //get end score
                $end = Fpt_Global::getRedisPaging($offset, $limit);
                //get key cache
                $keyCache    = Fpt_Global::makeCacheKey(self::LIST_TEAM_NEWS, $teamID);
                //get total
                $return['total']   = intval($redisInstance->zCount($keyCache, '-inf', '+inf'));
                //check total data
                if($return['total'] > 0)
                {
                    //get revert score: latest will be show first
                    $return['data']   = $redisInstance->zRange($keyCache, $offset, $end);
                }
            } 
        }
        catch(Exception $ex)
        {
            //log error
            Fpt_Log::sendlog(4, $ex->getCode(), $ex->getMessage(), $ex->getTraceAsString(), null, null);
        }
        return $return;
    }
    
    /**
     * @name get object reference by IDs
     * @param int ($articleID, $limit, $offset)
     * @return array
     * @author PhongTX
     * @modify PhuongTN
     */
    public function getObjectReferenceByArticleID($articleID, $limit, $offset = 0)
    {
        try 
        {
            $return = array();
            //get instance redis
            $redisReferInstance = Fpt_Global::getRedisInstance(0, $articleID);
            
            if($redisReferInstance !== false)
            {
                //get key cache
                $keyRefer  = Fpt_Global::makeCacheKey(self::LIST_OBJECT_REFERENCES, $articleID);
                //count total
                $return['total'] = intval($redisReferInstance->zCount($keyRefer, '-inf', '+inf'));
                //check total
                $return['data'] = array();
                if($return['total'] > 0)
                {
                    //get redis page
                    $end = Fpt_Global::getRedisPaging($offset, $limit);
                    $return['data'] = $redisReferInstance->zRevRange($keyRefer, $offset, $end);
                }
            }
        } 
        catch (Exception $ex) 
        {
            //log error
            Fpt_Log::sendlog(4, $ex->getCode(), $ex->getMessage(), $ex->getTraceAsString(), null, null);
        }
        return $return;
    }

    /**
     * @name get object topic by articleID
     * @param int ($articleID, $limit, $offset)
     * @return array
     * @author PhongTX
     * @modify PhuongTN
     */
    public function getTopicsByArticleID($articleID)
    {
        try 
        {
            $return = array();
            //get instance redis
            $redisInstance = Fpt_Global::getRedisInstance(0, $articleID);
            if($redisInstance !== false)
            {
                //get key cache
                $keyRefer  = Fpt_Global::makeCacheKey(self::LIST_OBJECT_TOPICS, $articleID);
                //count total
                $total = $redisInstance->hLen($keyRefer);
                //check total
                if($total > 0)
                {
                    //get all topic
                    $data = $redisInstance->hGetAll($keyRefer);
                    if(is_array($data))
                    {
                        ksort($data);
                        foreach($data as $k => $topicDetail)
                        {
                            $return[$k] = Zend_Json::decode($topicDetail);
                        }
                    }
                }
            }
        } 
        catch (Exception $ex) 
        {
            //log error
            Fpt_Log::sendlog(4, $ex->getCode(), $ex->getMessage(), $ex->getTraceAsString(), null, null);
        }
        return $return;
    }
    
    /**
     * get categories by article id: getall
     * @param int $articleID
     * @return type 
     * @author PhuongTN
     */
    public function getCategoriesByArticleID($articleID)
    {
        try 
        {
            $return = array();
            //get instance redis
            $redisInstance = Fpt_Global::getRedisInstance(0, $articleID);
            if($redisInstance !== false)
            {
                //get key cache
                $keyCates  = Fpt_Global::makeCacheKey(self::LIST_OBJECT_CATEGORIES, $articleID);
                //count total
                $total = $redisInstance->zCount($keyCates, '-inf', '+inf');
                //check total
                if($total > 0)
                {
                    $return = $redisInstance->zRevRange($keyCates, 0, -1);
                }
            }
        } 
        catch (Exception $ex) 
        {
            //log error
            Fpt_Log::sendlog(4, $ex->getCode(), $ex->getMessage(), $ex->getTraceAsString(), null, null);
        }
        return $return;
    }
    
    /**
     * get hot by article id: getall
     * @param int $articleID
     * @return type 
     * @author PhuongTN
     */
    public function getHotInfoByArticleID($articleID)
    {
        try 
        {
            $return = array();
            //get instance redis
            $redisInstance = Fpt_Global::getRedisInstance(0, $articleID);
            if($redisInstance !== false)
            {
                //get key cache
                $keyCates  = Fpt_Global::makeCacheKey(self::LIST_OBJECT_HOTS, $articleID);

                //count total
                $total = $redisInstance->zCount($keyCates, '-inf', '+inf');
                //check total
                if($total > 0)
                {
                    //get redis page
                    $end = Fpt_Global::getRedisPaging(0, 0);//get all
                    $return = $redisInstance->zRevRange($keyCates, $offset, $end);
                }
            }
        } 
        catch (Exception $ex) 
        {
            //log error
            Fpt_Log::sendlog(4, $ex->getCode(), $ex->getMessage(), $ex->getTraceAsString(), null, null);
        }
        return $return;
    }
    
    /**
     * Get list article by hot
     * @param array $arrParams (category_id,article_type,limit,offset,site_id)
     * @return array $arrReturn
     * @author  : phongtx@fpt.net
     */
    public function getListArticleByHot($arrParams)
    {
        //default return
        $return = array('total' => 0, 'data' => NULL);
        try
        {
            //get instance redis for this cate
            $redisInstance = Fpt_Global::getRedisInstance(0, $arrParams['category_id']);
            if($redisInstance !== false)
            {
                //get end score
                $end = Fpt_Global::getRedisPaging($arrParams['offset'], $arrParams['limit']);
                //get key cache
                $arrayHot   = array($arrParams['category_id'], 'hot');
                $keyCache    = Fpt_Global::makeCacheKey(self::LIST_TOP_NEWS, $arrayHot);
                //get total
                $return['total']   = $redisInstance->zCount($keyCache, '-inf', '+inf');
                //check total data
                if($return['total'] > 0)
                {
                    //get range score: low display_order show first
                    $return['data']   = $redisInstance->zRange($keyCache, $arrParams['offset'], $end);
                }
            }
        }
        catch(Exception $ex)
        {
            //log error
            Fpt_Log::sendlog(4, $ex->getCode(), $ex->getMessage(), $ex->getTraceAsString(), null, null);
        }
        return $return;
    }
    
    /**
     * them bai dang hen gio
     * @param array $articleDetail (public_time, article_id)
     * @return bool 
     */
    public function addScheduleArticle($articleDetail)
    {
        $return = false;
        try
        {
            //add list news on cate
            $redisInstance = Fpt_Global::getRedisInstance();
            if($redisInstance !== false)
            {
                $keyCate    = Fpt_Global::makeCacheKey(self::SCHEDULE_ARTICLES);
                $return     = $redisInstance->zAdd($keyCate, $articleDetail['publish_time'], $articleDetail['article_id']);     
            }
        }
        catch(Exception $ex)
        {
            //log error
            Fpt_Log::sendlog(4, $ex->getCode(), $ex->getMessage(), $ex->getTraceAsString(), null, null);
        }
        return $return;
    }
    
    /**
     * lay danh sach bai dang hen gio
     * @return type 
     */
    public function getScheduleArticles()
    {
        $return = array();
        try
        {
            //add list news on cate
            $redisInstance = Fpt_Global::getRedisInstance();
            $keyCate    = Fpt_Global::makeCacheKey(self::SCHEDULE_ARTICLES);
            $timeStart  = time();
            $return     = $redisInstance->zRangeByScore($keyCate, '-inf', $timeStart); 
        }
        catch(Exception $ex)
        {
            //log error
            Fpt_Log::sendlog(4, $ex->getCode(), $ex->getMessage(), $ex->getTraceAsString(), null, null);
        }
        return $return;
    }
    
   
    public function getRangeArticle()
    {
        $return = array();
        try
        {
            $redisInstance = Fpt_Global::getRedisInstance();
            $key        = Fpt_Global::makeCacheKey(self::MAX_ARTICLE_ID);
            $return     = $redisInstance->get($key); 
        }
        catch(Exception $ex)
        {
            //log error
            Fpt_Log::sendlog(4, $ex->getCode(), $ex->getMessage(), $ex->getTraceAsString(), null, null);
        }
        return $return;
    }
     
    public function setRangeArticle($maxID, $minID = 0)
    {
        $return = array();
        try
        {
            $redisInstance = Fpt_Global::getRedisInstance();
            $key        = Fpt_Global::makeCacheKey(self::MAX_ARTICLE_ID);
            $return     = $redisInstance->set($key, $maxID); 
        }
        catch(Exception $ex)
        {
            //log error
            Fpt_Log::sendlog(4, $ex->getCode(), $ex->getMessage(), $ex->getTraceAsString(), null, null);
        }
        return $return;
    }
    
    public function incrRangeArticle()
    {
        $return = array();
        try
        {
            $redisInstance = Fpt_Global::getRedisInstance();
            $key        = Fpt_Global::makeCacheKey(self::MAX_ARTICLE_ID);
            $return     = $redisInstance->incr($key);   
        }
        catch(Exception $ex)
        {
            //log error
            Fpt_Log::sendlog(4, $ex->getCode(), $ex->getMessage(), $ex->getTraceAsString(), null, null);
        }
        return $return;
    }
}